﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace YShop
{
    public class ExpressionBuilder<TEntity>
    {
        private Expression<Func<TEntity,bool>> _expression { get; set; }
        public ParameterExpression Params { get; set; }

        public ExpressionBuilder()
        {
            Params = Expression.Parameter(typeof(TEntity), "t");
            //var body = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
            //_expression = (Expression<Func<TEntity, bool>>)Expression.Lambda(body, Params);
        }

        private LambdaExpression InternalAndOrExpression(bool condition, LambdaExpression exp2, bool isAndAlso)
        {
            if (condition == false) return _expression;
            if (_expression == null) 
            {
                _expression = (Expression<Func<TEntity, bool>>)exp2;
            }
            else
            {
                var newParameters = _expression.Parameters.Select((a, b) => Expression.Parameter(a.Type, a.Name /*$"new{b}"*/)).ToArray();

                var left = new NewExpressionVisitor(newParameters, _expression.Parameters.ToArray()).Replace(_expression.Body);
                var right = new NewExpressionVisitor(newParameters, exp2.Parameters.ToArray()).Replace(exp2.Body);
                var body = isAndAlso ? Expression.AndAlso(left, right) : Expression.OrElse(left, right);
                _expression = (Expression<Func<TEntity, bool>>)Expression.Lambda(_expression.Type, body, newParameters);
            }
            return _expression;
        }
        private LambdaExpression InternalNotExpression(bool condition)
        {
            if (condition == false) return _expression;
            if (_expression == null) return null;

            var newParameters = _expression.Parameters.Select((a, b) => Expression.Parameter(a.Type, a.Name /*$"new{b}"*/)).ToArray();
            var body = Expression.Not(_expression.Body);
            _expression = (Expression<Func<TEntity, bool>>)Expression.Lambda(_expression.Type, body, newParameters);
            return _expression;
        }

        #region TEntity
        /// <summary>
        /// 使用 and 拼接两个 lambda 表达式
        /// </summary>
        /// <returns></returns>
        public  Expression<Func<TEntity, bool>> And(Expression<Func<TEntity, bool>> exp2) => And(true, exp2);
        /// <summary>
        /// 使用 and 拼接两个 lambda 表达式
        /// </summary>
        /// <param name="condition">true 时生效</param>
        /// <param name="exp2"></param>
        /// <returns></returns>
        public Expression<Func<TEntity, bool>> And(bool condition, Expression<Func<TEntity, bool>> exp2) => (Expression<Func<TEntity, bool>>)InternalAndOrExpression(condition, exp2, true);

        /// <summary>
        /// 使用 or 拼接两个 lambda 表达式
        /// </summary>
        /// <returns></returns>
        public  Expression<Func<TEntity, bool>> Or(Expression<Func<TEntity, bool>> exp2) => Or(true, exp2);
        /// <summary>
        /// 使用 or 拼接两个 lambda 表达式
        /// </summary>
        /// <param name="condition">true 时生效</param>
        /// <param name="exp2"></param>
        /// <returns></returns>
        public  Expression<Func<TEntity, bool>> Or(bool condition, Expression<Func<TEntity, bool>> exp2) => (Expression<Func<TEntity, bool>>)InternalAndOrExpression(condition, exp2, false);

        /// <summary>
        /// 将 lambda 表达式取反
        /// </summary>
        /// <param name="condition">true 时生效</param>
        /// <returns></returns>
        public  Expression<Func<TEntity, bool>> Not(bool condition = true) => (Expression<Func<TEntity, bool>>)InternalNotExpression(condition);
        #endregion

        public Func<TEntity, bool> Compile()
        {
            return _expression.Compile();
        }

        public Expression<Func<TEntity, bool>> Result()
        {
            return _expression;
        }

        internal class NewExpressionVisitor : ExpressionVisitor
        {
            ParameterExpression[] _newParameters;
            ParameterExpression[] _oldParameters;
            public NewExpressionVisitor(ParameterExpression newParam, ParameterExpression oldParam) : this(new[] { newParam }, new[] { oldParam }) { }
            public NewExpressionVisitor(ParameterExpression[] newParams, ParameterExpression[] oldParams)
            {
                this._newParameters = newParams;
                this._oldParameters = oldParams;
            }
            public Expression Replace(Expression exp) => this.Visit(exp);

            protected override Expression VisitParameter(ParameterExpression node)
            {
                for (var a = 0; a < _oldParameters.Length; a++)
                    if (_oldParameters[a] == node)
                        return _newParameters[a];
                return node;
            }
        }

        internal class TestParameterExpressionVisitor : ExpressionVisitor
        {
            public bool Result { get; private set; }

            protected override Expression VisitParameter(ParameterExpression node)
            {
                if (!Result) Result = true;
                return node;
            }
        }
    }
}
